package com.ankang.util.rfid;

import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class RF218x {
    private static final byte E_OK = 0x00;
    private static final byte E_RET = 0x01;
    private static final byte E_TIMEOUT = 0x02;
    private static final byte E_MOREDATA = 0x03;
    private static final byte E_BUF_OVERFLW = 0x04;
    private static final byte E_NOTAG = (byte)0xfb;

    private static final int IR_LEN = 0;
    private static final int IR_ADR = 1;
    private static final int IR_RCMD = 2;
    private static final int IR_STAT = 3;
    private static final int IR_DAT = 4;

    //命令发送等待接收的间隔
    public int recvInterval = 50;
    //接收超时. 都是局域网, 可以短点
    public int recvTimeout = 100;
    //轮询时命令之间的间隔
    public int cmdInterval = 20;
    //意外断开重连间隔
    public int connectInterval = 1000;
    //连接超时 (局域网500ms都嫌多)
    public int connectTimeout = 1000;

    /**
     * 上下文对象, 爱绑啥绑啥
     */
    public Object context;
    /**
     * poll 模式下读卡事件
     *
     * 事件传入的list是复用的. 如果要保留list内部的卡号, 需要复制出来.
     */
    public RF218xEvent<List<String>> OnTagsRead;
    /**
     * poll 模式下 连接状态改变事件
     */
    public RF218xEvent<CnnState> OnConnectionStateChanged;

    public enum CnnState{
        Lost,
        Start,
        Connected,
        Failed
    }

    private Socket _s;
    private InetAddress _tgt;
    private int _port;


    


    private static short get_crc (byte[] buf, int start, int len){
        int crc = 0xFFFF;  // 内部用int计算
        int end = len + start;
        
        for (int i = start; i < end; ++i) {
            crc ^= (buf[i] & 0xFF);
            
            for (int j = 0; j < 8; ++j) {
                if ((crc & 1) != 0) {
                    crc = (crc >>> 1) ^ 0x8408;
                } else {
                    crc = crc >>> 1;
                }
            }
        }
        
        return (short)crc;
    }
    int get_packlen (){
        int ret  = ibuf[0]+1;
        ret &= 0xFF;
        return ret;
    }
    private boolean check_pack_crc (){
        var nbytes = get_packlen();

        var c_crc = get_crc (ibuf,0,nbytes-2);
        var p_crc = (ibuf[nbytes-1]<<8) | (ibuf[nbytes-2] & 0xff);
        
        var ret = p_crc == c_crc;

        return ret;
    }

    public void connect () throws Exception
    {
        if (isConnected())
            return;
        var iaddr = new InetSocketAddress(_tgt, _port);
        
        var s = new Socket();
        try{
            s.connect(iaddr, connectTimeout);
        }catch (Exception e){
            s.close();
            throw e;
        }
        _s = s;

    }

    public boolean isConnected ()
    {
        if (_s == null)
            return false;
        if (!_s.isConnected()){
            inner_close();
            return false;
        }

        return true;
    }

    public void close () throws Exception{
        if (poll_running){
            throw new Exception("无法在连续读卡的途中关闭连接");
        }
            
        inner_close(false);
    }
    private void inner_close (){
        inner_close(true);
    }
    private void inner_close (boolean passive){
        if (_s != null){
            if (!_s.isClosed()){

                try{
                    _s.close();
                }catch (Exception e){
                    //nothing todo
                }finally{
                    _s = null;
                }
            }
        }

        clear_ibuf();
    }

    //
    private static byte[] _inventory = new byte[]{
        0x04, 0x00, 0x01,(byte) 0xdb ,0x4b
    };
    private boolean write (byte[] data) 
    {
        return write (data,0,data.length);
    }

    private boolean write (byte[] data, int s, int n) {
        if (!isConnected())
            return false;
        try{
            _s.getOutputStream().write(data, s, n);
        }catch (Exception e){
            inner_close();
            return false;
        }
        return true;
    }

    private byte[] ibuf = new byte[1024];
    private int nibuf = 0;

    private void clear_ibuf (){
        nibuf = 0;
    }

    private int read () {
        if (!isConnected())
            return -1;

        try{
            _s.setSoTimeout(recvTimeout);
            var n =  _s.getInputStream().read(ibuf, nibuf, ibuf.length-nibuf);
            nibuf+= n;
            return n;
        }catch (SocketTimeoutException  e){
            return 0;
        }catch(Exception e){
            inner_close();
            return -1;
        }
    }

    private int end_use_pack () {
        var iibuf = get_packlen();
        
        if (iibuf < nibuf){
            for (var i = iibuf; i < nibuf;++i){
                ibuf[i-iibuf] = ibuf[i];
            }

            nibuf -= iibuf;
            return iibuf;

        }else if(iibuf == nibuf){
            nibuf = 0;
            return iibuf;
        }else {//iibuf > nibuf
            if (nibuf != 0)
                return -1;//不太对

            return 0;
        }
    }
    //尝试接收一个完整的数据报,
    private int read_pack () {
        int iret =0;

        while (true){

            //无数据/有数据但数据不足的时候读数据
            boolean rd;
            if(iret == 0)
                rd = nibuf == 0;
            else
                rd = nibuf >= get_packlen();
            int nrd = 0;
            if(rd){
                nrd = read ();
                if (nrd == 0){//超时, 也可能远程关闭了
                    if(!isConnected())
                        inner_close();//远程关闭了, 这边也关

                    clear_ibuf();
                    return -1;
                }
            }

            iret = get_packlen();
            if (nibuf < iret)
                continue;//缓冲区字节数不足, 读取下一轮

            
            if (check_pack_crc())
                return iret;

            return -2;//crc error
        }
    }
    
    private void purgeInput () {

        try{
            //榨干所有未读取的数据
            var stm = _s.getInputStream();
            for (var n = stm.available();n > 0; n = stm.available())
                stm.read(ibuf);
            //清空ibuf
            clear_ibuf();
        }catch (Exception e){
            inner_close();
        }       
    }

    StringBuilder sb = new StringBuilder(24);
    
    private String toHex (byte[] buf, int s, int n){
        var end = s+n;
        sb.setLength(0);
        for (var i = s;i < end;++i){
            sb.append (String.format("%02X", buf[i]));
        }

        var ret = sb.toString();
        sb.setLength(0);
        return ret;
    }
    private int extract_tags (List<String> results) {
        var nTags = ibuf[IR_DAT];
        if (nTags == 0)
            return 0;

        var iibuf = IR_DAT+1;

        for (var i = 0; i < nTags;++i){
            if (iibuf >= nibuf)
                return -1;//理论上不会到这儿
            var len = ibuf[iibuf];
            var val = toHex(ibuf,++iibuf , len);
            iibuf+=len;

            results.add (val);
        }


        return nTags;
    }

    private int inventory(List<String> results, int rcvInterval) {
        if (rcvInterval < 0)
            rcvInterval = 10;//保底10

        if (!isConnected())
            return 0;

        // purge input buffer b4 send command
        purgeInput();

        if (!write (_inventory)){
            return 0;
        }

        sleep(rcvInterval);

        var n = results.size();
        var p = read_pack();
        if (0 > p)
            return 0;
        
        var st = ibuf[IR_STAT] & 0xFF;
        if (st == E_NOTAG){
            return 0;
        }else if (st > E_BUF_OVERFLW){
            return -st;
        }

        if (0 >extract_tags(results)){
            return 0;//数据报错误
        }
        while (E_MOREDATA == st){
            end_use_pack();
            p = read_pack();
            if (0 > p)
                break;

            if (0>extract_tags(results)){
                return 0;//数据报错误
            }
            st = ibuf[IR_STAT];
        }

        if (st > E_BUF_OVERFLW)
            return -st;

        return results.size()-n;
    }
    
    private static String[] empty = new String[0];

    /**
     * 公用的cache
     */
    private List<String> tag_cache = new ArrayList<>();
    public String[] inventory () throws Exception{
        if (poll_running){
            throw new Exception ("poll is running");
        }
        tag_cache.clear();
        var n = inventory(tag_cache, recvInterval);
        if (n < 0){
            throw new Exception ("读卡器异常, 状态码=" + Integer.toString(-n));
        }
        if (n == 0)
            tag_cache.clear();

        if (tag_cache.size() == 0)
            return empty;

        var ret = new String[tag_cache.size()];
        tag_cache.toArray(ret);
        tag_cache.clear();
        return ret;
    }


    public void init (InetAddress target, int port){
        _tgt = target;
        _port = port;
    }

    private void onCnnChange (CnnState reason){
        if (OnConnectionStateChanged != null)
            OnConnectionStateChanged.Invoke(this, reason);
    }
    private void onTags (List<String> tags){
        if (tags.size()==0 || OnTagsRead == null)
            return;

        OnTagsRead.Invoke(this, tags);
    }

    private boolean poll_running;
    private Thread th_poll;

    private void sleep (int ms){//妈的智障一样的异常处理
        try{
            Thread.sleep(ms);
        }catch (Exception e){

        }
    }
    private void doPoll () {

        while(poll_running){
            if (!isConnected()){
                try{
                    onCnnChange(CnnState.Start);
                    connect();
                }catch (Exception e){
                }
                if (!isConnected() && poll_running){
                    onCnnChange(CnnState.Failed);
                    sleep (connectInterval);//连接失败, 等一会儿再连
                }
                else{
                    onCnnChange(CnnState.Connected);
                }

                continue;
            }

            tag_cache.clear();
            var n = inventory(tag_cache, recvInterval);
            if (n < 1){
                if (!isConnected())
                    onCnnChange(CnnState.Lost);

                continue;
            }

            onTags(tag_cache);

            sleep(cmdInterval);
        }
    }

    public boolean isPolling()
    {
        return poll_running;
    }

    public void startPoll () throws Exception{
        if (!isConnected())
            throw new Exception ("读卡器未连接");

        if (poll_running)
            return;

        poll_running= true;
        th_poll = new Thread(()-> this.doPoll());

        th_poll.start();
    }

    public void stopPoll () throws Exception{
        if (!poll_running)
            return;

        poll_running = false;
        th_poll.join();
        th_poll = null;
    }

    /**
     * for test only
     */
    public void waitPollEnd () throws Exception{
        if (th_poll != null)
            th_poll.join();
    }
}

